package cn.itcast.nems.journal.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.lang.Assert;
import cn.itcast.ic.common.bean.Page;
import cn.itcast.ic.common.exception.v2.BizExceptionProducer;
import cn.itcast.nems.approval.bpm.eventstrategy.BpmEventStrategy;
import cn.itcast.nems.approval.dao.entity.ApprovalConfig;
import cn.itcast.nems.approval.dao.entity.ApprovalForm;
import cn.itcast.nems.approval.enumeration.ApprovalErrorBodyEnum;
import cn.itcast.nems.approval.enumeration.ApprovalFormStateEnum;
import cn.itcast.nems.approval.service.ApprovalConfigService;
import cn.itcast.nems.approval.service.ApprovalFormFlowService;
import cn.itcast.nems.approval.service.ApprovalFormService;
import cn.itcast.nems.approval.service.ApprovalFormService.ApproveResult;
import cn.itcast.nems.approval.service.ApprovalFormService.JournalType;
import cn.itcast.nems.base.api.ClazzApi;
import cn.itcast.nems.base.api.SchoolApi;
import cn.itcast.nems.base.api.SubjectApi;
import cn.itcast.nems.base.dto.ClazzDTO;
import cn.itcast.nems.base.dto.SchoolDTO;
import cn.itcast.nems.base.dto.SubjectDTO;
import cn.itcast.nems.common.PageQueryModel;
import cn.itcast.nems.common.account.AccountHolder;
import cn.itcast.nems.common.enumeration.BizNumberTypeEnum;
import cn.itcast.nems.common.enumeration.NemsErrorBodyEnum;
import cn.itcast.nems.common.service.BizNumberService;
import cn.itcast.nems.common.util.LocalDateTimeUtils;
import cn.itcast.nems.common.utils.WrapperUtil;
import cn.itcast.nems.journal.dto.*;
import cn.itcast.nems.journal.enumeration.JournalFinalErrorBodyEnum;
import cn.itcast.nems.journal.enumeration.JournalStateEnum;
import cn.itcast.nems.journal.service.JournalFinalLineService;
import cn.itcast.nems.journal.service.JournalFinalService;
import cn.itcast.nems.journal.util.JournalUtil;
import cn.itcast.nems.manager.activity.BpmWorkflowManager;
import cn.itcast.nems.manager.util.CustomBeanUtil;
import cn.itcast.nems.studentrecord.bo.JournalClazzModel;
import cn.itcast.nems.studentrecord.constant.OrderSourceEnum;
import cn.itcast.nems.studentrecord.constant.ReferenceTypeEnum;
import cn.itcast.nems.studentrecord.dao.condition.ApprovalType;
import cn.itcast.nems.studentrecord.dao.dataobject.ClazzLockRecordDO;
import cn.itcast.nems.studentrecord.dao.dataobject.JournalFinalDO;
import cn.itcast.nems.studentrecord.dao.dataobject.JournalRepeatDO;
import cn.itcast.nems.studentrecord.dao.entity.JournalFinal;
import cn.itcast.nems.studentrecord.dao.entity.JournalFinalLine;
import cn.itcast.nems.studentrecord.dao.entity.StudentRecord;
import cn.itcast.nems.studentrecord.dao.mapper.JournalFinalMapper;
import cn.itcast.nems.studentrecord.dto.StudentRecordWithStudentDTO;
import cn.itcast.nems.studentrecord.dto.change.StudentRecordJournalFinalDTO;
import cn.itcast.nems.studentrecord.enumeration.StudentRecordErrorBodyEnum;
import cn.itcast.nems.studentrecord.service.StudentRecordFinalService;
import cn.itcast.nems.studentrecord.service.studentrecord.StudentRecordService;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import jakarta.validation.constraints.NotNull;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections.CollectionUtils;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.*;
import java.util.stream.Collectors;

/**
 * <p>
 * 结课凭单-头 服务实现类
 * </p>
 *
 * @author CodeGenerator
 * @since 2024-01-04
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class JournalFinalServiceImpl implements JournalFinalService, BpmEventStrategy {
    private final JournalFinalMapper journalFinalMapper;
    
    private final ApprovalFormFlowService approvalFormFlowService;
    private final ApprovalFormService approvalFormService;
    private final BizNumberService bizNumberService;
    private final JournalFinalLineService journalFinalLineService;
    private final StudentRecordService studentRecordService;
    private final SchoolApi schoolApi ;
    private final SubjectApi subjectApi ;
    private final ClazzApi clazzApi;
    private final ApprovalConfigService approvalConfigService ;
    private final BpmWorkflowManager bpmWorkflowManager ;
    private final StudentRecordFinalService studentRecordFinalService ;
    @Override
    public String getStrategyName() {
        return JournalType.FINAL.name();
    }
    
    /**
     * 知会的处理
     */
    @Override
    public void inform(String formID, String targetAssignee) {
        log.debug("收到结课凭单知会！凭单ID：{}，被知会人：{}", formID, targetAssignee);
        approvalFormFlowService.inform(formID, targetAssignee);
    }

    /**
     * 审批完成的处理
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public Runnable finish(String formID) {
        ApprovalForm approvalForm = approvalFormService.getByBusinessKey(formID);
        String journalId = approvalForm.getJournalId();
        log.info("【审批完成】查询审批单详情：{}", JSON.toJSONString(approvalForm));
        // 处理凭单自己的业务逻辑
        final JournalFinalDO finalDO = journalFinalMapper.findById(journalId);
        log.info("【审批完成】查询结课凭单详情：{}", JSON.toJSONString(finalDO));
        final List<JournalFinalLineDTO> lines = this.journalFinalLineService.listByJournalFinalId(journalId);
        log.info("【审批完成】查询结课凭单行列表：{}", JSON.toJSONString(lines));
        lines.forEach(line -> {
            StudentRecordJournalFinalDTO recordJournalFinalDTO = this.transferToStudentRecordJournalFinalDTO(finalDO, line) ;
            studentRecordFinalService.finish(recordJournalFinalDTO);
            log.info("【审批完成】处理结课后学籍和订单参数：{}", JSON.toJSONString(recordJournalFinalDTO));
        });
        journalFinalMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalFinal>()
                                                            .eq(JournalFinal::getId, journalId)
                                                            .set(JournalFinal::getState, ApprovalFormStateEnum.FINISHED.name())
        );
        log.info("【审批完成】修改结课凭单状态 ID：{}， 修改状态：{}", journalId, ApprovalFormStateEnum.FINISHED.name());
        // 处理本地的审批流程单
        approvalFormService.finish(formID);
        return null;
    }

    private StudentRecordJournalFinalDTO  transferToStudentRecordJournalFinalDTO(JournalFinalDO finalDO , JournalFinalLineDTO line) {
        StudentRecordJournalFinalDTO  recordJournalFinalDTO = new StudentRecordJournalFinalDTO() ;
        recordJournalFinalDTO.setJournalId(finalDO.getId());
        recordJournalFinalDTO.setJournalLineId(line.getId());
        recordJournalFinalDTO.setClazzId(finalDO.getClazzId());
        recordJournalFinalDTO.setClazzName(finalDO.getClazzName());
        recordJournalFinalDTO.setStudentRecordId(line.getStudentRecordId());
        recordJournalFinalDTO.setInDate(line.getInDate());
        recordJournalFinalDTO.setSettlementDate(line.getSettlementDate());
        recordJournalFinalDTO.setActualAmountDue(line.getActualAmountDue());
        recordJournalFinalDTO.setActualServiceDays(line.getActualServiceDays());
        return recordJournalFinalDTO ;
    }
       
    /**
     * 审批的处理
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void approve(String formID, ApproveResult result, String comment, final String form, Integer applyType) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        JournalFinalDO finalDO = journalFinalMapper.findById(approvalForm.getJournalId());
        Assert.notNull(finalDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "结课凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(finalDO.getState())
                        || ApprovalFormStateEnum.APPROVING.name().equalsIgnoreCase(finalDO.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_PENDING_APPROVING));
        List<String> srIds = journalFinalLineService.listObjs(
                new LambdaQueryWrapper<JournalFinalLine>()
                        .select(JournalFinalLine::getStudentRecordId)
                        .eq(JournalFinalLine::getJournalFinalId, finalDO.getId())
                        .eq(JournalFinalLine::getDeletedFlag, false)
                        .eq(JournalFinalLine::getJournalFinalId, approvalForm.getJournalId()),
                Object::toString
        );
        if (Objects.requireNonNull(result) == ApproveResult.REJECT) {
            journalFinalMapper.update(null,
                    new LambdaUpdateWrapper<JournalFinal>().eq(JournalFinal::getId, approvalForm.getJournalId())
                            .set(JournalFinal::getState, ApprovalFormStateEnum.REJECTED)
            );

            this.studentRecordService.unlocks(new HashSet<>(srIds), ReferenceTypeEnum.FINAL, approvalForm.getJournalId());
            log.info("【结课凭单】学籍释放锁列表：{}", JSON.toJSONString(srIds));
//            // 解锁班级
            clazzApi.unlock(finalDO.getClazzId());
            JournalFinal updateEntity = new JournalFinal();
            updateEntity.setId(formID);
            updateEntity.setState(ApprovalFormStateEnum.REJECTED.name());
            journalFinalMapper.updateById(updateEntity);
        } else {
            JournalStateEnum journalStateEnum = JournalStateEnum.APPROVING;
            //  1：审批（默认））2：驳回到发起人）3：驳回上一级）
            if (applyType != null && applyType == 2) {
                journalStateEnum = JournalStateEnum.DRAFT;
                this.studentRecordService.unlocks(new HashSet<>(srIds), ReferenceTypeEnum.FINAL, approvalForm.getJournalId());
                clazzApi.unlock(finalDO.getClazzId());
            } else if (applyType != null && applyType == 3) {
                journalStateEnum = JournalStateEnum.PENDING;
            }
            journalFinalMapper.update(null,
                    new LambdaUpdateWrapper<JournalFinal>()
                            .eq(JournalFinal::getId, finalDO.getId())
                            .set(JournalFinal::getState, journalStateEnum.name())
            );
        }
        // 处理审批单的业务逻辑
        approvalFormService.approve(formID, result, comment, form, applyType);
    }
    
    /**
     * 凭单撤回的处理
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void cancel(String formID) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formID));
        Assert.isTrue(approvalForm.getCreatedBy().equalsIgnoreCase(AccountHolder.getRequiredAccount()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_CREATED_BY));

        JournalFinalDO finalDO = journalFinalMapper.findById(approvalForm.getJournalId());
        Assert.notNull(finalDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "结课凭单ID：" + approvalForm.getJournalId()));
        Assert.isTrue(ApprovalFormStateEnum.PENDING.name().equalsIgnoreCase(finalDO.getState()),
                BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_CANCEL_NO_PENDING));
        journalFinalMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalFinal>().eq(JournalFinal::getId, approvalForm.getJournalId())
                                                            .set(JournalFinal::getState, JournalStateEnum.DRAFT.name()));
        // 解锁班级
        clazzApi.unlock(finalDO.getClazzId());
        // 解锁学籍
        List<Object> recordIds = journalFinalLineService.listObjs(
                                                    new LambdaQueryWrapper<JournalFinalLine>()
                                                            .select(JournalFinalLine::getStudentRecordId)
                                                            .eq(JournalFinalLine::getDeletedFlag, false)
                                                            .eq(JournalFinalLine::getJournalFinalId, approvalForm.getJournalId())
        );
        log.info("【结课凭单-撤销流程】查询学籍ID列表：{}", JSON.toJSONString(recordIds));
        if (CollectionUtils.isNotEmpty(recordIds)) {
            studentRecordService.unlocks(recordIds.stream().map(Object::toString).collect(Collectors.toSet()) , ReferenceTypeEnum.FINAL , approvalForm.getJournalId());
            log.info("【结课凭单-撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(recordIds));
        }
        approvalFormService.cancel(formID);
        
    }
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void rollbackRequester(String formID) {
        ApprovalForm approvalForm = approvalFormService.getById(formID);
        Assert.notNull(approvalForm, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "审批单ID：" + formID));

        JournalFinalDO finalDO = journalFinalMapper.findById(approvalForm.getJournalId());
        Assert.notNull(finalDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "结课凭单ID：" + approvalForm.getJournalId()));
        if (finalDO.getState().equals(JournalStateEnum.DRAFT.name())) {
            return;
        }
        journalFinalMapper.update(null,
                new LambdaUpdateWrapper<JournalFinal>().eq(JournalFinal::getId, approvalForm.getJournalId())
                        .set(JournalFinal::getState, JournalStateEnum.DRAFT.name()));
        // 解锁班级
        clazzApi.unlock(finalDO.getClazzId());
        // 解锁学籍
        List<Object> recordIds = journalFinalLineService.listObjs(
                new LambdaQueryWrapper<JournalFinalLine>()
                        .select(JournalFinalLine::getStudentRecordId)
                        .eq(JournalFinalLine::getDeletedFlag, false)
                        .eq(JournalFinalLine::getJournalFinalId, approvalForm.getJournalId())
        );
        log.info("【结课凭单-撤销流程】查询学籍ID列表：{}", JSON.toJSONString(recordIds));
        if (CollectionUtils.isNotEmpty(recordIds)) {
            studentRecordService.unlocks(recordIds.stream().map(Object::toString).collect(Collectors.toSet()) , ReferenceTypeEnum.FINAL , approvalForm.getJournalId());
            log.info("【结课凭单-撤销流程】解锁学籍ID列表：{}", JSON.toJSONString(recordIds));
        }
//        approvalFormService.cancel(formID);
    }
    /**
     * 催办
     */
    @Override
    public void remind(String formID, String targetAssignee) {
        log.debug("收到结课凭单的催办提醒！凭单ID：{}，被催办人：{}", formID, targetAssignee);
        approvalFormService.remind(formID, targetAssignee);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public int refreshLines(String journalId) {
        JournalFinalDO finalDO = journalFinalMapper.findById(journalId) ;
        Assert.notNull(finalDO ,  BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"结课凭单ID："+  journalId)) ;
        if(!JournalStateEnum.DRAFT.name().equalsIgnoreCase(finalDO.getState())) {
            log.info("【刷新结课凭单行数据】凭单ID：{} ,凭单状态：{} 不做处理！" ,  finalDO.getId() , finalDO.getState() );
            return  0 ;
        }
        if(this.validateJournalLineDifferent(journalId , finalDO.getClazzId() )) {
            log.info("【重读凭单】刷新行数据！");
            List<StudentRecordWithStudentDTO> inClazzStudentRecords = studentRecordService.findInClazzStudentRecord(finalDO.getClazzId());
            // 作废历史行数据
            journalFinalLineService.update(null ,
                                                    new LambdaUpdateWrapper<JournalFinalLine>()
                                                            .set(JournalFinalLine::getDeletedFlag ,true )
                                                            .eq(JournalFinalLine::getJournalFinalId , journalId)) ;
            int quantity = CollectionUtil.isNotEmpty(inClazzStudentRecords) ? inClazzStudentRecords.size() : 0 ;
            journalFinalMapper.update(null ,
                                                    new LambdaUpdateWrapper<JournalFinal>()
                                                            .eq(JournalFinal::getId , journalId )
                                                            .set(JournalFinal::getFinalQuantity ,quantity)
                                                            .set(JournalFinal::getStudentQuantity ,quantity)) ;
            if(quantity == 0) {
                return  0 ;
            }
            finalDO.setStudentQuantity(quantity);
            finalDO.setFinalQuantity(quantity);
            // 保存行
            saveLines(journalId, finalDO.getActualFinalDate(), inClazzStudentRecords);
            return  inClazzStudentRecords.size() ;
        }
        return finalDO.getStudentQuantity()    ;
    }

    @Override
    public IPage<JournalFinalDO> pageList(PageQueryModel<JournalFinalModel> pageQueryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalRepeatDO> page =
                new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(pageQueryModel.getPageNumber());
        page.setSize(pageQueryModel.getPageSize());
        JournalFinalModel finalModel = Objects.nonNull(pageQueryModel.getCondition()) ?
                pageQueryModel.getCondition() :
                new JournalFinalModel();
        JournalFinalDO finalDO = CustomBeanUtil.copyProperties(finalModel, new JournalFinalDO());
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), finalDO, JournalType.FINAL.name(), finalModel.getApprovalType());
        log.info("【结课凭单】分页查询条件：{}", JSON.toJSONString(finalDO));
        ApprovalType approvalType = finalDO.getApprovalType();
        if ( approvalType == ApprovalType.ALL) {
            if (!AccountHolder.hasEmployeeGroups()) {
                return null;
            }
            finalDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        }
        return journalFinalMapper.findPageList(page, finalDO);

    }
    @Override
    public Page<JournalClazzDTO> findClazzList4PageSearch(PageQueryModel<JournalClazzModel> queryModel) {
        com.baomidou.mybatisplus.extension.plugins.pagination.Page<JournalFinalDO> page = new com.baomidou.mybatisplus.extension.plugins.pagination.Page<>();
        page.setCurrent(queryModel.getPageNumber());
        page.setSize(queryModel.getPageSize());
        JournalClazzModel clazzModel = Objects.nonNull(queryModel.getCondition()) ? queryModel.getCondition() : new JournalClazzModel();
        JournalFinalDO repeatDO = new JournalFinalDO();
        repeatDO.setClazzName(clazzModel.getClazzName());
        JournalUtil.invokeByApprovalType(this.getJournalIdFormIds(), repeatDO, JournalType.FINAL.name(),clazzModel.getApprovalType());
        //        ApprovalType approvalType = repeatDO.getApprovalType();
        //        if (approvalType == ApprovalType.ALL) {
        //            if (!AccountHolder.hasEmployeeGroups()) {
        //                return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), 0, null);
        //            }
        //            repeatDO.setEmployeeGroups(AccountHolder.getEmployeeGroups());
        //        }
        IPage<JournalFinalDO> pageData = journalFinalMapper.findClazzPageList(page, repeatDO);
        List<JournalClazzDTO> list = CollectionUtil.isNotEmpty(pageData.getRecords()) ?
                pageData.getRecords().stream().map(item -> new JournalClazzDTO(item.getClazzId(), item.getClazzName())).toList() : null;
        return cn.itcast.ic.common.bean.Page.of(queryModel.getPageNumber(), queryModel.getPageSize(), Long.valueOf(pageData.getTotal()).intValue(), list);
    }

    private List<String> getJournalIdFormIds() {
        ApprovalConfig config = approvalConfigService.getByJournalCode(JournalType.FINAL.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, ApprovalFormService.JournalType.FINAL.getCnName()));
        return bpmWorkflowManager.findWaitApproval(config.getBpmProcessKey(), AccountHolder.getRequiredAccount());
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String save(@NonNull JournalFinalEditDTO journalFinalEditDTO) {
        ClazzDTO clazzDTO = clazzApi.queryByClassId(journalFinalEditDTO.getClazzId());
        Assert.notNull(clazzDTO, BizExceptionProducer.produce(JournalFinalErrorBodyEnum.CLAZZ_NOT_FOUND));
        this.validateCreateParam(journalFinalEditDTO,clazzDTO);
        LocalDate actualFinalDate = journalFinalEditDTO.getActualFinalDate();
        String id = bizNumberService.next(BizNumberTypeEnum.FINAL);
        journalFinalEditDTO.setId(id);
        List<StudentRecordWithStudentDTO> inClazzStudentRecords = studentRecordService.findInClazzStudentRecord(journalFinalEditDTO.getClazzId());
        Assert.notEmpty(inClazzStudentRecords, BizExceptionProducer.produce(JournalFinalErrorBodyEnum.CLAZZ_STUDENT_RECORD_EMPTY, clazzDTO.getClassName()));
        journalFinalEditDTO.setStudentQuantity(inClazzStudentRecords.size());
        // 保存头
        saveHeader(clazzDTO, journalFinalEditDTO);
        // 保存行
        saveLines(journalFinalEditDTO.getId(), actualFinalDate, inClazzStudentRecords);
        return journalFinalEditDTO.getId();
    }



    private void validateCreateParam(JournalFinalEditDTO editDTO ,ClazzDTO clazzDTO) {
        Assert.isTrue(clazzDTO.getStartDate().isBefore(
            editDTO.getActualFinalDate()),
                                                    BizExceptionProducer.produce(
                                                            JournalFinalErrorBodyEnum.STUDENT_START_DATE_OR_FINALDATE_INVALID,
                                                            LocalDateTimeUtils.formatLocalDate(editDTO.getActualFinalDate()),
                                                            LocalDateTimeUtils.formatLocalDate(clazzDTO.getStartDate())
                                                    )
        );
        SchoolDTO schoolDTO  = schoolApi.findById(editDTO.getCampusId()) ;
        Assert.notNull(schoolDTO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "校区ID："+ editDTO.getCampusId())) ;
        SubjectDTO subjectDTO  = subjectApi.findById(editDTO.getSubjectId()) ;
        Assert.notNull(subjectDTO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "学科ID："+ editDTO.getCampusId())) ;

        List list = studentRecordService.findNotInClazzRecord(clazzDTO.getClassId(), null);
        Assert.isFalse(CollectionUtil.isNotEmpty(list) , BizExceptionProducer.produce(JournalFinalErrorBodyEnum.CLAZZ_STUDENT_RECORD_EMPTY,clazzDTO.getClassName()));
    }


    private List<JournalFinalClazzLockRecordDTO> findLockStudentRecordList(String journalId, Integer clazzId) {
        JournalFinalDO finalDO = journalFinalMapper.findById(journalId);
        Assert.notNull(finalDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "凭单ID：" + journalId));
        List list = studentRecordService.findNotInClazzRecord(clazzId, null);
        log.info("【修改、提交审批】查询在途学籍列表：{}" ,  JSON.toJSONString(list , SerializerFeature.DisableCircularReferenceDetect));
        if (CollectionUtil.isNotEmpty(list)) {
            JournalFinalClazzDTO finalClazzDTO = this.getClazzInfoByClazzId(clazzId);
            return finalClazzDTO.getLockRecordList();
        }
        return  null ;
    }

    /**
     * 检验凭单行是否存在“在途学籍”
     * @param journalId 凭单ID
     * @param clazzId 班级ID
     * @return 是否存在
     */
    private Boolean validateJournalLineDifferent(String journalId, Integer clazzId) {
        JournalFinalDO finalDO = journalFinalMapper.findById(journalId);
        Assert.notNull(finalDO, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "凭单ID：" + journalId));
        List<JournalFinalLine> lines = journalFinalLineService.list(
                                                    new LambdaQueryWrapper<JournalFinalLine>()
                                                            .eq(JournalFinalLine::getJournalFinalId, journalId)
                                                            .eq(JournalFinalLine::getDeletedFlag, false)
        );

        int lineCount = Objects.nonNull(lines) ? lines.size() : 0;
        List<StudentRecordWithStudentDTO> inClazzStudentRecords = studentRecordService.findInClazzStudentRecord(clazzId);
        int inClazzStudentRecordCount = Objects.nonNull(inClazzStudentRecords) ? inClazzStudentRecords.size() : 0;
        // 检查 数据库中凭单行数量和  页面参数中行数量是否一致
        if (lineCount != inClazzStudentRecordCount) {
            return true;
        }
        // 检查是否不存在该班学籍的情况
        List list = studentRecordService.findNotInClazzRecord(clazzId, null );
        if (CollectionUtil.isNotEmpty(list)) {
            return true;
        }
        return false;
    }

    /**
     * 
     * 功能描述：保存凭单行
     *
     * @param classDto 班级信息
     * @param journalFinalEditDTO 凭单信息
     * @return 凭单头ID
     * 
     * @author 刘曙
     *
     * @since 2024年1月4日
     *
     * @update:[变更日期YYYY-MM-DD][更改人姓名][变更描述]
     */
    private void saveHeader(ClazzDTO classDto, @NonNull JournalFinalEditDTO journalFinalEditDTO) {
        LocalDate actualFinalDate = journalFinalEditDTO.getActualFinalDate();
        JournalFinal headerEntity = new JournalFinal();
        headerEntity.setId(journalFinalEditDTO.getId());
        headerEntity.setSubjectId(classDto.getSubjectId());
        headerEntity.setSubjectName(classDto.getSubjectName());
        headerEntity.setCampusId(classDto.getSchoolId());
        headerEntity.setCampusName(classDto.getSchoolName());
        headerEntity.setModelId(classDto.getModelId());
        headerEntity.setModelName(classDto.getModelName());
        headerEntity.setClazzId(classDto.getClassId());
        headerEntity.setClazzName(classDto.getClassName());
        headerEntity.setStudentQuantity(journalFinalEditDTO.getStudentQuantity());
        headerEntity.setFinalQuantity(journalFinalEditDTO.getStudentQuantity());
        headerEntity.setStartDate(classDto.getStartDate());
        headerEntity.setPlanFinalDate(classDto.getPredictionGraduationDate());
        headerEntity.setActualFinalDate(actualFinalDate);
        headerEntity.setSpecialFlag(journalFinalEditDTO.getSpecialFlag());
        headerEntity.setState(JournalStateEnum.DRAFT.name());   // 默认状态
        headerEntity.setRemark(journalFinalEditDTO.getRemark());
        journalFinalMapper.insert(headerEntity);
        
    }
    
    /**
     * 
     * 功能描述：保存凭单行
     *
     * @param journalFinalId 凭单ID
     * @param actualFinalDate 实际结课日期
     * @param inclazzStudentRecords 在班学籍
     * 
     * @author 刘曙
     *
     * @since 2024年1月4日
     *
     * @update:[变更日期YYYY-MM-DD][更改人姓名][变更描述]
     */
    private List<JournalFinalLine>  saveLines(String journalFinalId, @NonNull LocalDate actualFinalDate,
                                              List<StudentRecordWithStudentDTO> inclazzStudentRecords) {
        List<JournalFinalLine> lines = inclazzStudentRecords.stream().map(sr -> {
            JournalFinalLine lineEntity = new JournalFinalLine();
            LocalDate inDate = Objects.nonNull(sr.getInDate()) ? sr.getInDate().toLocalDate() : null;
            // 结课日期不能在学生进班日期之前
            lineEntity.setJournalFinalId(journalFinalId);
            lineEntity.setStudentRecordId(sr.getId());
            lineEntity.setInDate(inDate);
            lineEntity.setSettlementDate(actualFinalDate);
            lineEntity.setActualServiceDays(LocalDateTimeUtils.calServiceDays(inDate, actualFinalDate)); // 计算服务天数
            lineEntity.setActualAmountDue(sr.getActualAmountDue());
            lineEntity.setTransferFromAmount(sr.getTransferFromAmount());
            lineEntity.setPaidAmount(sr.getPaidAmount());
            lineEntity.setBalanceAmountDue(sr.getBalanceAmountDue());
            lineEntity.setRefundAmount(sr.getRefundAmount());
            return lineEntity;
        }).toList();
        journalFinalLineService.saveBatch(lines);
        return lines ;
    }
    
    @Override
    public JournalFinalDTO getById(String id) {
        JournalFinalDO finalDO  = journalFinalMapper.findById(id);
        Assert.notNull(finalDO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"结课凭单ID：" + id)) ;
        JournalFinalDTO finalDTO =  CustomBeanUtil.copyProperties(finalDO, new JournalFinalDTO())   ;
        ClazzDTO clazzDTO = clazzApi.queryByClassId(finalDO.getClazzId()) ;
        Assert.notNull(clazzDTO , BizExceptionProducer.produce(NemsErrorBodyEnum .DATA_EXIST ,"班级ID：" + finalDTO.getClazzId())) ;
        finalDTO.setStartDate( clazzDTO.getStartDate());
        finalDTO.setPlanFinalDate( clazzDTO.getPredictionGraduationDate());
        //  获取审批单ID
        ApprovalForm approvalForm = approvalFormService.getOne(
                                                    new WrapperUtil<ApprovalForm>().limitLaseOne("created_datetime").eq(ApprovalForm::getJournalId, id)
                                                            .eq(ApprovalForm::getDeletedFlag, false)
                                                            .notIn(ApprovalForm::getState, ApprovalFormStateEnum.CANCELED.name())
        );
        finalDTO.setFormId(Objects.nonNull(approvalForm) ? approvalForm.getId() : null);
        return finalDTO ;
    }

    @Override
    public JournalFinalClazzDTO getClazzInfoByClazzId(Integer clazzId) {
        JournalFinalClazzDTO finalClazzDTO = new JournalFinalClazzDTO() ;
        ClazzDTO clazzDTO  =   clazzApi.queryByClassId(clazzId) ;
        Assert.notNull(clazzDTO  , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"班级ID："+  clazzDTO))   ;
        List<StudentRecordWithStudentDTO> inClazzStudentRecords = studentRecordService.findInClazzStudentRecord(clazzId);
        finalClazzDTO.setClazzId(clazzId);
        finalClazzDTO.setStudentQuantity(CollectionUtil.isNotEmpty(inClazzStudentRecords) ?inClazzStudentRecords.size() : 0);
        finalClazzDTO.setStartDate(clazzDTO.getStartDate());
        finalClazzDTO.setPlanFinalDate(clazzDTO.getPredictionGraduationDate());
        List<ClazzLockRecordDO> recordList = studentRecordService.findClazzLockRecordListByClazzId(clazzId ,null) ;
        List<JournalFinalClazzLockRecordDTO> list  =    CollectionUtil.isNotEmpty(recordList)?
                recordList.stream().map(item ->
                        CustomBeanUtil.copyProperties(item, new JournalFinalClazzLockRecordDTO())).toList() :
                Collections.EMPTY_LIST;
        log.info("【结课凭单】查询在途学生列表：{}" ,JSON.toJSONString(list , SerializerFeature.DisableCircularReferenceDetect));
        finalClazzDTO.setLockRecordList(  list );
        finalClazzDTO.setLockQuantity(list.size());
        return finalClazzDTO;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JournalFinalClazzLockRecordDTO> update(JournalFinalEditDTO journalFinalEditDTO) {
        // 校验，非“待审批”状态的凭单不允许更新
        JournalFinalDO finalDO  = journalFinalMapper.findById(journalFinalEditDTO.getId()) ;
        Assert.notNull(finalDO , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST ,"凭单ID："+journalFinalEditDTO.getId() ));
        Assert.isTrue( JournalStateEnum.DRAFT.name().equalsIgnoreCase(finalDO.getState()) ,BizExceptionProducer.produce(JournalFinalErrorBodyEnum.JOURNAL_STATE_NOT_DRAFT));
        List<JournalFinalClazzLockRecordDTO>  lockRecordList= this.findLockStudentRecordList(journalFinalEditDTO.getId() , journalFinalEditDTO.getClazzId() ) ;
        if(CollectionUtil.isNotEmpty(lockRecordList)) {
            log.info("【提交审批】存在在途学生：{}" ,JSON.toJSONString(lockRecordList ,  SerializerFeature.DisableCircularReferenceDetect));
            return  lockRecordList ;
        }
        ClazzDTO clazzDTO = clazzApi.queryByClassId(journalFinalEditDTO.getClazzId());
        Assert.notNull(clazzDTO, BizExceptionProducer.produce(JournalFinalErrorBodyEnum.CLAZZ_NOT_FOUND));
        this.validateCreateParam(journalFinalEditDTO , clazzDTO);
        // 更新结课凭单
        journalFinalMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalFinal>()
                                                            .set(JournalFinal::getActualFinalDate, journalFinalEditDTO.getActualFinalDate())
                                                            .set(JournalFinal::getSpecialFlag ,journalFinalEditDTO.getSpecialFlag() )
                                                            .set(JournalFinal::getRemark ,journalFinalEditDTO.getRemark() )
                                                            .eq(JournalFinal::getId, journalFinalEditDTO.getId())
        );
        List<JournalFinalLine> lines = journalFinalLineService.list(
                                                    new LambdaQueryWrapper<JournalFinalLine>()
                                                            .eq(JournalFinalLine::getJournalFinalId, journalFinalEditDTO.getId())
                                                            .eq(JournalFinalLine::getDeletedFlag, false)
        );
        if(this.validateJournalLineDifferent(finalDO.getId() , finalDO.getClazzId() )) {
            log.info("【重读凭单】刷新行数据！");
            List<StudentRecordWithStudentDTO> inClazzStudentRecords = studentRecordService.findInClazzStudentRecord(finalDO.getClazzId());
            // 作废历史行数据
            journalFinalLineService.update(null ,
                                                    new LambdaUpdateWrapper<JournalFinalLine>()
                                                            .set(JournalFinalLine::getDeletedFlag ,true )
                                                            .eq(JournalFinalLine::getJournalFinalId , finalDO.getId())
            ) ;
            int quantity = CollectionUtil.isNotEmpty(inClazzStudentRecords) ? inClazzStudentRecords.size() : 0 ;
            journalFinalMapper.update(null ,
                                                    new LambdaUpdateWrapper<JournalFinal>()
                                                            .eq(JournalFinal::getId , finalDO.getId() )
                                                            .set(JournalFinal::getStudentQuantity ,quantity)
                                                            .set(JournalFinal::getStudentQuantity ,quantity)
            ) ;
            finalDO.setStudentQuantity(quantity);
            // 保存行
            if(quantity > 0 ) {
                lines = saveLines(finalDO.getId(), finalDO.getActualFinalDate(), inClazzStudentRecords);
            }
        }
        if (!finalDO.getActualFinalDate().equals(journalFinalEditDTO.getActualFinalDate()) ) {
            Objects.requireNonNullElse(lines,
                                                    new ArrayList<JournalFinalLine>()).forEach(item -> {
                                                            item.setSettlementDate(journalFinalEditDTO.getActualFinalDate());
                                                            // 计算服务天数
                                                            item.setActualServiceDays(LocalDateTimeUtils.calServiceDays(item.getInDate(), journalFinalEditDTO.getActualFinalDate()));
                                                    }
            );
            journalFinalLineService.saveOrUpdateBatch(lines);
        }
        if (JournalStateEnum.DRAFT.name().equalsIgnoreCase(finalDO.getState())
                && JournalStateEnum.PENDING.name().equalsIgnoreCase(journalFinalEditDTO.getState())) {
            this.startWorkflow(finalDO);
        }
        return  Collections.EMPTY_LIST;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public List<JournalFinalClazzLockRecordDTO>   flow(String id) {
        JournalFinalDO finalDO = this.validateBeforeUpdate(id);
        List<JournalFinalClazzLockRecordDTO>  lockRecordList= this.findLockStudentRecordList(finalDO.getId() , finalDO.getClazzId() ) ;
        if(CollectionUtil.isNotEmpty(lockRecordList)) {
            log.info("【提交审批】存在在途学生：{}" ,JSON.toJSONString(lockRecordList ,  SerializerFeature.DisableCircularReferenceDetect));
            return  lockRecordList ;
        }
        this.startWorkflow(finalDO);
        return  Collections.EMPTY_LIST;

    }

    private void startWorkflow(JournalFinalDO finalDO) {
        ApprovalConfig config = approvalConfigService.getByJournalCode(JournalType.FINAL.name());
        Assert.notNull(config, BizExceptionProducer.produce(ApprovalErrorBodyEnum.MUST_CONFIG_APPROVAL_CONFIG, JournalType.FINAL.getCnName()));
        // 锁定班级
        clazzApi.lock(finalDO.getClazzId());
        this.lockOriginStudentRecord(journalFinalLineService.listByJournalFinalId(finalDO.getId()) );
        HashMap<String, Object> variables = this.getVariables( finalDO.getSpecialFlag());
        String result = approvalFormService.start(JournalType.FINAL, finalDO.getId(), variables, config.getBpmProcessKey());
        log.info("【提交】流程审批单详情：{}", result);
        int updateResult = journalFinalMapper.update(null,
                                                    new LambdaUpdateWrapper<JournalFinal>()
                                                            .eq(JournalFinal::getId, finalDO.getId())
                                                            .set(JournalFinal::getState, JournalStateEnum.PENDING.name())
        );
        Assert.isTrue(updateResult > 0,
                BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR, "升级凭单头ID：" + finalDO.getId()));
    }

    /**
     * <p>
     * 获取升级凭单工作流参数
     * </p>
     *
     * @param specialFlag                             是否特殊审批
     * @author zhouwei
     * @since 2023/12/8 1:33 PM
     **/
    private   HashMap<String, Object> getVariables( Boolean specialFlag) {
        HashMap<String, Object> map = new HashMap<>();
        // 1、办理事项  handle_matters  （ "线下转线上博学谷" ， "转班"）
        map.put("handle_matters", OrderSourceEnum.FINAL.getCname());
        // 2、是否特殊审批 special_approval （"true"， "false"）
        map.put("special_approval", Objects.isNull(specialFlag) || !specialFlag ? "false": "true");
        log.info("【获取工作流参数】参数：{}" , JSON.toJSONString(map));
        return map ;
    }

    /**
     * 将升级凭单-行对应的学籍标识为 ”上锁“状态
     * @param lines 升级凭单-行数据
     */
    private void lockOriginStudentRecord(@NotNull List<JournalFinalLineDTO> lines) {
        lines.forEach(item -> {
            StudentRecord studentRecord = studentRecordService.getById(item.getStudentRecordId());
            Assert.notNull(studentRecord, BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST, "学籍ID：" + item.getStudentRecordId()));
            Assert.isTrue(!studentRecord.getLockFlag(), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.STUDENT_RECORD_LOCKED, "学籍ID：" + item.getStudentRecordId()));
            studentRecordService.locks(Set.of(item.getStudentRecordId()) , ReferenceTypeEnum.FINAL , item.getJournalFinalId());
        });
    }


    private JournalFinalDO validateBeforeUpdate (String id ) {
        // 检查凭单是否存在及状态是否为 “PENDING”
        JournalFinalDO finalDO = journalFinalMapper.findById(id)  ;
        Assert.notNull(finalDO ,BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "凭单ID："+ id )) ;
        Assert.isTrue(JournalStateEnum.DRAFT.name().equals(finalDO.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.REQUEST_JOURNAL_RENEWAL_STATE_ERROR)) ;
        return finalDO ;
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public boolean removeById(String id) {
        JournalFinal journalFinal  = journalFinalMapper.selectById(id) ;
        Assert.notNull(journalFinal ,  BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_EXIST , "结课凭单ID："+ id)) ;
        Assert.isTrue(AccountHolder.getRequiredAccount().equalsIgnoreCase(journalFinal.getCreatedBy()) , BizExceptionProducer.produce(NemsErrorBodyEnum.DATA_DELETE_UNAUTHORIZED));
        Assert.isTrue(JournalStateEnum.DRAFT.name().equalsIgnoreCase(journalFinal.getState()), BizExceptionProducer.produce(StudentRecordErrorBodyEnum.WORKFLOW_NO_DRAFT_DELETE));
        // 作废凭单头
        int rst = journalFinalMapper.update(null , new LambdaUpdateWrapper<JournalFinal>().eq(JournalFinal::getId ,  id).set(JournalFinal::getDeletedFlag , true));
        Assert.isTrue(rst >0  ,BizExceptionProducer.produce(NemsErrorBodyEnum.DATABASE_UPDATE_ERROR ,"删除结课凭单"));
        // TODO 删除凭单行
        journalFinalLineService.update(null,
                                                    new LambdaUpdateWrapper<JournalFinalLine>()
                                                            .eq(JournalFinalLine::getJournalFinalId ,id )
                                                            .set(JournalFinalLine::getDeletedFlag ,true )
        ) ;
        return true ;
    }
    
}
